#!/usr/bin/env python

import sys
from kal_tokenizer import KalTokenizer
import pdb

class KalASTParseError(Exception):
    pass

class KalASTNode(object):
    def __init__(self,tokenizer):
        self.tokenizer = tokenizer
        self.line_no = tokenizer.line_no
        self.column_no = tokenizer.column_no
        self.parse()
        #print self.__class__.__name__
    def __str__(self):
        return self.__class__.__name__
    def print_tree(self,indent,name):
        sys.stdout.write(' '*indent + name + ': ' + self.__class__.__name__)
        for item in self.__dict__:
            if type(self.__dict__[item]) == list:
                sys.stdout.write('\n')
                for subitem in self.__dict__[item]:
                    subitem.print_tree(indent+2,item)
            elif isinstance(self.__dict__[item],KalASTNode):
                sys.stdout.write('\n')
                self.__dict__[item].print_tree(indent+2,item)
    def optional(self,*items):
        self.tokenizer.push()
        for item in items:
            if type(item) == str:
                if self.tokenizer.token == item:
                    self.tokenizer.next()
                    return item
            else:
                try:
                    return item(self.tokenizer)
                except:
                    pass
        self.tokenizer.pop()
        return None
    def require(self,*items):
        v = self.optional(*items)
        if v == None:
            self.syntax_error(items)
        else:
            return v
    def optional_multiple(self,*items):
        v = self.optional(*items)
        rv = []
        while v != None:
            rv.append(v)
            v = self.optional(*items)
        return rv
    def require_multiple(self,*items):
        rv = self.optional_multiple(*items)
        if rv == []:
            self.syntax_error(items)
        else:
            return rv
    def syntax_error(self,items):
        raise KalASTParseError, "expected: " + "|".join([str(item.__name__) for item in items]) + " on line " + str(self.tokenizer.line_no) + " column " + str(self.tokenizer.column_no)
            
class KalPrimitive(KalASTNode):
    def convert(self,token):
        return token
    def parse(self):
        if self.tokenizer.token_type != self.token_type:
            raise KalASTParseError, "expected: " + self.pretty_token_type
        else:
            self.value = self.convert(self.tokenizer.token)
            self.tokenizer.next()
    def print_tree(self,indent,name):
        sys.stdout.write(' '*indent + name + ': ' + self.__class__.__name__ + ' = '+ str(self.value))
        
class KalString(KalPrimitive):
    token_type = 'STRING'
    pretty_token_type = 'quoted string'

class KalPunctuation(KalPrimitive):
    token_type = 'PUNCTUATION'
    pretty_token_type = 'operator'

class KalBinOp(KalPrimitive):
    token_type = 'BINOP'
    pretty_token_type = 'binary operator'
    
class KalInteger(KalPrimitive):
    token_type = 'INTEGER'
    pretty_token_type = 'integer'
    def convert(self,token):
        return int(token)
class KalFloat(KalPrimitive):
    token_type = 'FLOAT'
    pretty_token_type = 'floating point number'
    def convert(self,token):
        return float(token)
class KalName(KalPrimitive):
    token_type = 'NAME'
    pretty_token_type = 'variable name or identifier'
class KalEOL(KalPrimitive):
    token_type = 'NEWLINE'
    pretty_token_type = 'end of line (newline)'
    def print_tree(self,indent,name):
        sys.stdout.write(' '*indent + name + ': ' + self.__class__.__name__ + ' = <EOL>')
class KalEOF(KalPrimitive):
    token_type = 'EOF'
    pretty_token_type = 'end of the file'

def parse(code,node_class):
    tokenizer = KalTokenizer(code)
    tokenizer.next()
    #print tokenizer.token
    return node_class(tokenizer)